﻿using System;
using System.Linq;
using FftComplex = FftSharp.Complex;
using FftTransform = FftSharp.Transform;

namespace Ursa.Music._163.Utils;

public class Visualizer
{
    //private int _m;
    private double[] _sampleData;
    private DateTime _lastTime;
    private SecondOrderDynamicsForArray _dynamics;
    private int _size;

    /// <summary>
    /// 采样数据
    /// </summary>
    public double[] SampleData => _sampleData;

    /// <summary>
    /// 尺寸
    /// </summary>
    public int Size
    {
        get => _size; set
        {
            if (!(Get2Flag(value)))
                throw new ArgumentException("长度必须是 2 的 n 次幂");

            _size = value;
            _sampleData = new double[value];
            _dynamics = new SecondOrderDynamicsForArray(1, 1, 1, 0, value / 2);
        }
    }

    public int OutputSize => Size / 2;

    public Visualizer(int size)
    {
        if (!(Get2Flag(size)))
            throw new ArgumentException("大小必须是 2 的 n 次幂", nameof(size));

        _lastTime = DateTime.Now;
        _sampleData = new double[size];
        _dynamics = new SecondOrderDynamicsForArray(1, 1, 1, 0, size / 2);
    }

    /// <summary>
    /// 判断是否是 2 的整数次幂
    /// </summary>
    /// <param name="num"></param>
    /// <returns></returns>
    private bool Get2Flag(int num)
    {
        if (num < 1)
            return false;
        return (num & num - 1) == 0;
    }

    public void PushSampleData(double[] waveData)
    {
        if (waveData.Length > _sampleData.Length)
        {
            Array.Copy(waveData, waveData.Length - _sampleData.Length, _sampleData, 0, _sampleData.Length);
        }
        else
        {
            Array.Copy(_sampleData, waveData.Length, _sampleData, 0, _sampleData.Length - waveData.Length);
            Array.Copy(waveData, 0, _sampleData, _sampleData.Length - waveData.Length, waveData.Length);
        }
    }

    public void PushSampleData(double[] waveData, int count)
    {
        if (count > _sampleData.Length)
        {
            Array.Copy(waveData, count - _sampleData.Length, _sampleData, 0, _sampleData.Length);
        }
        else
        {
            Array.Copy(_sampleData, count, _sampleData, 0, _sampleData.Length - count);
            Array.Copy(waveData, 0, _sampleData, _sampleData.Length - count, count);
        }
    }

    /// <summary>
    /// 获取频谱数据 (数据已经删去共轭部分)
    /// </summary>
    /// <returns></returns>
    public double[] GetSpectrumData()
    {
        DateTime now = DateTime.Now;
        double deltaTime = (now - _lastTime).TotalSeconds;
        _lastTime = now;

        int len = _sampleData.Length;
        FftComplex[] data = new FftComplex[len];

        for (int i = 0; i < len; i++)
            data[i] = new FftComplex(_sampleData[i], 0);

        FftTransform.FFT(data);

        int halfLen = len / 2;
        double[] spectrum = new double[halfLen];           // 傅里叶变换结果左右对称, 只需要取一半
        for (int i = 0; i < halfLen; i++)
            spectrum[i] = data[i].Magnitude / len;

        var window = new FftSharp.Windows.Bartlett();
        window.Create(halfLen);
        window.ApplyInPlace(spectrum, false);

        //return spectrum;
        return _dynamics.Update(deltaTime, spectrum);
    }

    /// <summary>
    /// 取指定频率内的频谱数据
    /// </summary>
    /// <param name="spectrum">源频谱数据</param>
    /// <param name="sampleRate">采样率</param>
    /// <param name="frequency">目标频率</param>
    /// <returns></returns>
    public static double[] TakeSpectrumOfFrequency(double[] spectrum, double sampleRate, double frequency)
    {
        double frequencyPerSampe = sampleRate / spectrum.Length;

        int lengthInNeed = (int)(Math.Min(frequency / frequencyPerSampe, spectrum.Length));
        double[] result = new double[lengthInNeed];
        Array.Copy(spectrum, 0, result, 0, lengthInNeed);
        return result;
    }

    /// <summary>
    /// 简单的数据模糊
    /// </summary>
    /// <param name="data">数据</param>
    /// <param name="radius">模糊半径</param>
    /// <returns>结果</returns>
    public static double[] GetBlurry(double[] data, int radius)
    {
        double[] GetWeights(int radius)
        {
            double Gaussian(double x) => Math.Pow(Math.E, (-4 * x * x));        // 憨批高斯函数

            int len = 1 + radius * 2;                         // 长度
            int end = len - 1;                                // 最后的索引
            double radiusF = (double)radius;                    // 半径浮点数
            double[] weights = new double[len];                 // 权重

            for (int i = 0; i <= radius; i++)                 // 先把右边的权重算出来
                weights[radius + i] = Gaussian(i / radiusF);
            for (int i = 0; i < radius; i++)                  // 把右边的权重拷贝到左边
                weights[i] = weights[end - i];

            double total = weights.Sum();
            for (int i = 0; i < len; i++)                  // 使权重合为 0
                weights[i] = weights[i] / total;

            return weights;
        }

        void ApplyWeights(double[] buffer, double[] weights)
        {
            int len = buffer.Length;
            for (int i = 0; i < len; i++)
                buffer[i] = buffer[i] * weights[i];
        }


        double[] weights = GetWeights(radius);
        double[] buffer = new double[1 + radius * 2];

        double[] result = new double[data.Length];
        if (data.Length < radius)
        {
            Array.Fill(result, data.Average());
            return result;
        }


        for (int i = 0; i < radius; i++)
        {
            Array.Fill(buffer, data[i], 0, radius + 1);      // 填充缺省
            for (int j = 0; j < radius; j++)                 // 
            {
                buffer[radius + 1 + j] = data[i + j];
            }

            ApplyWeights(buffer, weights);
            result[i] = buffer.Sum();
        }

        for (int i = radius; i < data.Length - radius; i++)
        {
            for (int j = 0; j < radius; j++)                 // 
            {
                buffer[j] = data[i - j];
            }

            buffer[radius] = data[i];

            for (int j = 0; j < radius; j++)                 // 
            {
                buffer[radius + j + 1] = data[i + j];
            }

            ApplyWeights(buffer, weights);
            result[i] = buffer.Sum();
        }

        for (int i = data.Length - radius; i < data.Length; i++)
        {
            Array.Fill(buffer, data[i], 0, radius + 1);      // 填充缺省
            for (int j = 0; j < radius; j++)                 // 
            {
                buffer[radius + 1 + j] = data[i - j];
            }

            ApplyWeights(buffer, weights);
            result[i] = buffer.Sum();
        }

        return result;
    }
}